/**
 * Copyright (c) 2010 Go Media (www.gomedia.co.za)
 * Released under MIT License (http://www.opensource.org/licenses/mit-license.php)
 */

(function($) {

    /**
     * @author Go Media www.gomedia.co.za | info@gomedia.co.za
     * @class Go Media Web Kit (GMWK)
     * @constructor
     * @member _global_
     * @param {Object} Options Constructor options
     * @param {String} Options.sTemplatePath Path to HTML templates
     * @version Beta
     */
    function gmwk() {
    
        var self = this;
        var sSharedClassPath = "js/classes/";
        var sLocalClassPath = "js/classes/";
        var sTemplatePath = "templates/";
        var sVersion = "0.1";
        var bLiveEnvironment = false;
        var bDebugStatus = true;
        var jqTemplateCache;
        var jqStageContainer;
        var TemplateCache = {};
        var nTemplateCounter = 0;
        var aStack = [];
        
        /**
         * Namespace for classes
         * @name gmwk.classes
         * @namespace
         */
        this.classes = {};
        
        /**
         * Extend GMWK with application specific utility functions, see utils.js
         * @public
         */
        this.utils = function() {};
        
        /** 
         * Configure application and bootstrap, override this function in config.js
         * @public
         */
        this.config = function() {};

        /** 
         * Set path to shared Javascript classes
         * @param {String} [sMyLibPath="js/classes/"] Path to shared classes
         * @public
         */
        this.setSharedClassPath = function(sMyLibPath) {
            if (typeof sMyLibPath !== "undefined") {
                sSharedClassPath = sMyLibPath;
            }
        };

        /** 
         * Set path to Javascript classes specific to an application
         * @param {String} [sMyLocalLibPath="js/classes/"] Path to classes
         * @public
         */
        this.setLocalClassPath = function(sMyLocalLibPath) {
            if (typeof sMyLocalLibPath !== "undefined") {
                sLocalClassPath = sMyLocalLibPath;
            }
        };

        /** 
         * Set path to HTML templates
         * @param {String} [sMyTemplatePath="templates/"] Path to templates
         * @public
         */
        this.setTemplatePath = function(sMyTemplatePath) {
            if (typeof sMyTemplatePath !== "undefined") {
                sTemplatePath = sMyTemplatePath;
            }
        };

        /** 
         * Set application version, used to control browser cache on live site
         * @param {String} [sMyVersion="0.1"] Application version
         * @public
         */
        this.setVersion = function(sMyVersion) {
            if (typeof sMyVersion !== "undefined") {
                sVersion = sMyVersion;
            }
        };
        
        /** 
         * If live environment is false getVersion returns a random string
         * @param {Boolean} [bMyLiveEnvironment=false] Live environment
         * @public
         */
        this.setLiveEnvironment = function(bMyLiveEnvironment) {
            if (typeof bMyLiveEnvironment !== "undefined") {
                bLiveEnvironment = bMyLiveEnvironment;
            }
        };

        /** 
         * If debug status is false global console object will be overwritten
         * @param {Boolean} [bMyDebugStatus=true] Debug status
         * @public
         */
        this.setDebugStatus = function(bMyDebugStatus) {
            if (typeof bMyDebugStatus !== "undefined") {
                bDebugStatus = bMyDebugStatus;
            }
        };

        /** 
         * The element that is used as the template cache.
         * @param {jQuery} jqMyTemplateContainer Template cache container wrapped in jQuery
         * @public
         */
        this.setTemplateCache = function(jqMyTemplateContainer) {
            if (typeof jqMyTemplateContainer !== "undefined") {
                jqTemplateCache = jqMyTemplateContainer;
            }
        };

        /** 
         * The element that is used as the stage container 
         * @param {jQuery} jqMyStageContainer Stage container wrapped in jQuery
         * @public
         */
        this.setStageContainer = function(jqMyStageContainer) {
            if (typeof jqMyStageContainer !== "undefined") {
                jqStageContainer = jqMyStageContainer;
            }
        };
        
        /**
         * Entry point into the application, called when DOM is ready
         * @public
         */
        this.run = function() {
            // Initialize console
            fInitConsole();
            // Set default template and stage containers
            self.setTemplateCache($("#gmwk_templates"));
            self.setStageContainer($("#gmwk_stage"));
            // Setup utility functions
            self.utils();
            // Configure and bootstrap
            self.config();
        };
        
        /**
         * Get application version, return random string if not in live environment
         * @public
         */
        this.getVersion = function() {
            if (bLiveEnvironment) {
                return sVersion;
            } else {
                return ((new Date()).getTime().toString());
            }
        };
    
        /**
         * Get debug status
         * @public
         * @returns {Boolean} Debug status
         */
        this.getDebugStatus = function() {
            return bDebugStatus;
        };

        /**
         * Get template container
         * @public
         * @returns {jQuery} Template container
         */
        this.getTemplateContainer = function() {
           return jqTemplateCache;
        };
        
        /**
         * Get stage container
         * @public
         * @returns {jQuery} Stage container
         */
        this.getStageContainer = function() {
            return jqStageContainer;
        };

        /**
         * Remove all elements from stage container
         * @public
         */        
        this.clearStage = function(){
            jqStageContainer.empty();
        };

        /**
         * Create an object instance, fetch class definition if not cached.
         *    Try to fetch class definition from local library path first.
         *    If not found then try to fetch from shared library.
         * @param {String} sClassName Name of the class to create an instance of, case sensitive
         * @param {Object} Options Options will be passed to class contructor
         * @public
         */
        this.create = function(sClassName, Options) {
            if (typeof(Options) == "undefined") {
               Options = {};
            }
            if (self.classes[sClassName] != undefined) {
                return fCreateClass(sClassName, Options);
            } else {
                fLoadClass(sClassName)
                return fCreateClass(sClassName, Options);
            }
        };

        /**
         * Get a cached template from the template container.
         *    If the template is not cached load it from the template path.
         *    Use cloneTemplate to insert a template into the stage. 
         * @param {String} sTemplateName Name of the template.
         *    Template names should preferably match a class name.
         *    It is not required, and some classes may have many templates.
         * @public
         * @returns {jQuery} Cached template
         */
        this.getTemplate = function(sTemplateName) {
            if (TemplateCache[sTemplateName] == undefined) {
                var jqTemplate = jqTemplateCache.find(".gmwk_template_" + sTemplateName);
                if (jqTemplate.length == 0) {
                    // Load template with AJAX call
                    fLoadTemplate(sTemplateName);
                } else {
                    // Template already loaded into the DOM
                    TemplateCache[sTemplateName] = jqTemplate;
                }
            }
            return TemplateCache[sTemplateName];
        };
        
        /**
         * Copy template from template cache to the given container.
         *    Each template will be given a unique ID with format: "gmwk_" + sTemplateName + "_" + nTemplateCounter.
         *    Each template will also be given the class "gmwk_object_container"
         *    TODO: Create third parameter Options to insert clone at different positions in jqContainer.
         * @param {sTemplateName} Name of the template to clone
         * @param {jqContainer} Name of the container to clone the template in
         * @public
         * @returns {jQuery} Reference to the cloned template container
         */
        this.cloneTemplate = function(sTemplateName, jqContainer) {
            nTemplateCounter++;
            self.getTemplate(sTemplateName)
                .clone()
                .attr("id", "gmwk_" + sTemplateName + "_" + nTemplateCounter)
                .addClass("gmwk_object_container")
                .appendTo(jqContainer);
            return jqContainer.find("#gmwk_" + sTemplateName + "_" + nTemplateCounter);
        }
        
        /**
         * Find closest object container to a given element
         * @param {jQuery} jqElement Search from this element
         * @public
         * @returns {jQuery} The closest element with class "gmwk_object_container"
         */
        this.closestObjectContainer = function(jqElement) {
            return jqElement.closest(".gmwk_object_container").attr("id");
        }
        
        /**
         * Define global window.console if it does not exist.  Overwrite console if not in debug mode
         * @private
         */
        function fInitConsole() {
            if (typeof window.console == 'undefined' || !bDebugStatus) {
                if (!window.console) {
                    window.console = {};
                }
                window.console.log = function(msg) {};
                window.console.info = function(msg) {};
                window.console.warn = function(msg) {};
                window.console.error = function(msg) {};
                window.console.time = function(msg) {};
                window.console.timeEnd = function(msg) {};
                window.console.profile = function(msg) {};
                window.console.profileEnd = function(msg) {};
                window.console.group = function(msg) {};
                window.console.groupEnd = function(msg) {};
                window.console.trace = function(msg) {};
                window.console.dir = function(msg) {};
            }
        }

        /**
         * Synchronously load template from template path and append to the template cache
         * @param {String} sTemplateName Name of the template to load
         * @public
         */
        function fLoadTemplate(sTemplateName) {
            $.ajax(
                {
                    url : sTemplatePath + sTemplateName + ".html?_=" + self.getVersion(),
                    success : function(Data) {
                        if (typeof TemplateCache[sTemplateName] == "undefined") { 
                            TemplateCache[sTemplateName] = {};
                        }
                        jqTemplateCache.append(Data);
                        var jqTemplate = jqTemplateCache.find("> :last");
                        TemplateCache[sTemplateName] = jqTemplate;
                    },
                    dataType: "html",
                    async : false
                }
            );
        }

        /**
         * Synchronously load class definition and eval.
         *    Classes must attach their definitions to gmwk.classes.sClassName
         *    Will try to load class from sLocalClassPath first.
         *    If class is not found on first try look in sSharedClassPath.
         * @param {String} sClassName Name of the class to load
         * @param {String} [aPath=sLocalClassPath] First path to load the class from
         * @private
         */
        function fLoadClass(sClassName, sPath) {
            if (typeof sPath == "undefined") {
                sPath = sLocalClassPath;
            }
            $.ajax(
                {
                    url : sPath + sClassName + ".js?_=" + self.getVersion(),
                    success : function(Data) {
                        //alert("here" + Data);
                        // Allow class to cache itself
                        eval(Data);
                    },
                    error : function(XMLHttpRequest, errorType, exception) {
                        // File not found in local store try lib
                        if (sPath == sLocalClassPath) {
                            fLoadClass(sClassName, sSharedClassPath);
                        // File not found in lib
                        } else {
                            alert(sClassName + " could not be found");
                        }
                    },
                    async : false
                }
            );
        }
        
        /**
         * Create a class instance
         * @param {String} sClassName Name of the class to create an instance
         * @param {Object} Options Options will be passed to the class contructor
         * @private
         * @returns {Object} Instance of the specified class
         */
        function fCreateClass(sClassName, Options) {
            Options.sClassName = sClassName;
            if (
                Options.sTemplate !== undefined && 
                Options.jqParentContainer !== undefined
            ) {
                Options.jqObjectContainer = self.cloneTemplate(Options.sTemplate, Options.jqParentContainer);
            }
            if (Options.bSingleton) {
                self[sClassName] = new self.classes[sClassName](Options);
            } else { 
                return new self.classes[sClassName](Options);
            }
        }
      
    }

    // Create GMWK global namespace
    window.gmwk = new gmwk();
    // Run application when DOM is loaded
    $(
        function() {
            window.gmwk.run();
        }
    );

})(jQuery);

